package com.easyaop.test.core.junit.v2;

import org.objectweb.asm.*;

import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class Gen implements Opcodes {
    private static AtomicInteger integer = new AtomicInteger(0);
    private List<Class> retList = new LinkedList<>();
    private List<Class[]> paramsList = new LinkedList<>();

    private MyLoader loader;

    public Gen() {
        loader = new MyLoader(this.getClass().getClassLoader());
    }

    public Gen add(Class ret, Class... params) {
        retList.add(ret);
        paramsList.add(params);
        return this;
    }

    public Class toClass() {

        String className = "com.MyGen__" + integer.incrementAndGet();
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        MethodVisitor methodVisitor;

        classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER
                , className.replaceAll("\\.", "/")
                , null, "java/lang/Object", null);

        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            methodVisitor.visitCode();
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            methodVisitor.visitInsn(RETURN);
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();
        }


        for (int i = 0, len = retList.size(); i < len; i++) {
            Class[] params = paramsList.get(i);
            Class ret = retList.get(i);
            String methodDescriptor = Type.getMethodDescriptor(Type.getType(ret), Arrays.stream(params)
                    .map(Type::getType).toArray(Type[]::new));

            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, ret.getSimpleName() + "__" + toStr(params), methodDescriptor, null, null);
            methodVisitor.visitCode();

            methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            methodVisitor.visitLdcInsn("test.. " + methodDescriptor);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

            retValue(ret, methodVisitor);

            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();
        }

        classWriter.visitEnd();

        byte[] bytes = classWriter.toByteArray();

        try {
            FileOutputStream so=new FileOutputStream("aaa_"+integer.intValue()+".class");
            so.write(bytes);
            so.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return loader.load(className, bytes);
    }

    private static void retValue(Class ret, MethodVisitor methodVisitor) {
        if (ret == void.class) {
            methodVisitor.visitInsn(RETURN);
        } else if (ret == boolean.class) {
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitInsn(IRETURN);
        } else if (ret == char.class) {
            methodVisitor.visitIntInsn(BIPUSH, 65);
            methodVisitor.visitInsn(IRETURN);
        } else if (ret == byte.class) {
            methodVisitor.visitIntInsn(BIPUSH, 11);
            methodVisitor.visitInsn(IRETURN);
        } else if (ret == short.class) {
            methodVisitor.visitIntInsn(BIPUSH, 12);
            methodVisitor.visitInsn(IRETURN);
        } else if (ret == int.class) {
            methodVisitor.visitIntInsn(BIPUSH, 13);
            methodVisitor.visitInsn(IRETURN);
        } else if (ret == long.class) {
            methodVisitor.visitLdcInsn(Long.valueOf(14));
            methodVisitor.visitInsn(LRETURN);
        } else if (ret == float.class) {
            methodVisitor.visitLdcInsn(Float.valueOf(21));
            methodVisitor.visitInsn(FRETURN);
        } else if (ret == double.class) {
            methodVisitor.visitLdcInsn( Double.valueOf(22));
            methodVisitor.visitInsn(DRETURN);
        } else if (ret == String.class) {
            methodVisitor.visitLdcInsn("str");
            methodVisitor.visitInsn(ARETURN);
        } else {
            methodVisitor.visitInsn(ACONST_NULL);
            methodVisitor.visitInsn(ARETURN);
        }
    }

    private static String toStr(Class[] params) {
        return Arrays.stream(params).map(c -> c.getSimpleName())
                .collect(Collectors.joining("_"));
    }

    private static class MyLoader extends ClassLoader {
        public MyLoader(ClassLoader classLoader) {
            super(classLoader);
        }
        public Class load(String name, byte[] bytes) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}
